#!/usr/bin/env /Library/ringojs/bin/ringo

/**
 * @fileoverview
 * Record the lines of code for the project and append the report in JSON
 * format to clocs_data.js for processing.
 *
 * Usage:
 * ./clocs.js 'This is a description of what's happened since last time.'
 */

// Store version description before anyone has a chance to play with arguments.
var desc = arguments[1];

// Imports:
var fs = require('fs'),
    command = require('ringo/subprocess').command,
    StringReader =
        require('/devel/snippets/javascript/ringo/string_reader').StringReader,
      
    pathToReport = './project_stats.json',
    stats = {
      date: Date.now(),
      clocs: {},
      sizes: {}
    };

if (! fs.exists('precompile.js')) {
  print('Precompile.js is not available.  Please compile and re-run.');
  system.exit(-1);
}

/**
 * Get the gzipped size of the given path.
 * @param {string} path The path of the file to measure.
 * @return {number} The size of the file in bytes.
 */
var getGZippedSize = function(path) {
  let size = 0,
      blockSize = 1024,
      zipPath = path + '.cloc_gzip',
      binary = require('binary'),
      buffer = new binary.ByteArray(blockSize),
      fIn = new java.io.FileInputStream(path),
      fOut = new java.io.FileOutputStream(zipPath),
      fZipped = new java.util.zip.GZIPOutputStream(fOut);
  
  let bytesRead = fIn.read(buffer, 0, blockSize);
  while(bytesRead > -1) {
    size += bytesRead;
    fZipped.write(buffer, 0, bytesRead);
    bytesRead = fIn.read(buffer, 0, blockSize);
  }
  
  let zippedSize = fOut.getChannel().size();
  
  fIn.close();
  fOut.close();

  fs.remove(zipPath);
  return zippedSize;
};

/*
 * Run various lines-of-code reports and store them in stats.clocs:
 *   - stats.clocs.project: lines of code for the full project
 *   - stats.clocs.client: lines of javascript code in ~/client-src/dkpoints
 *   - stats.clocs.tests: lines of test code in the client app (JS & HTML)
 *   - stats.clocs.precompile: lines of code in precompile.js
 *   - stats.clocs.server: lines of code in ~/WAR/
 *   - stats.clocs.backend: lines of javascript code in ~/WAR/src
 *   - stats.clocs.templates: lines of Soy template code in the client app.
 */

/**
 * Callback from the subprocess used to process stats.clocs.project.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerProjectClocs = function(results) {
  let languages = results.languages.language,
      project = stats.clocs.project = {};

  for (let language in languages) {
    let lang = languages[language],
        name = lang.@name.toString(),
        files = parseInt(lang.@files_count, 10),
        code = parseInt(lang.@code, 10),
        comment = parseInt(lang.@comment, 10),
        blank = parseInt(lang.@blank, 10),
        totalLines = code + comment + blank;
    
    project[name] = {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    };
  }

  let total = results.languages.total,
      sum_files = parseInt(total.@sum_files, 10),
      blank = parseInt(total.@blank, 10),
      comment = parseInt(total.@comment, 10),
      code = parseInt(total.@code, 10),
      totalLines = blank + comment + code;

  project.summary = {
    sum_files: sum_files,
    blank: blank,
    comment: comment,
    code: code,
    total: totalLines
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.server.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerDeployedClocs = function(results) {
  let languages = results.languages.language,
      deployed = stats.clocs.deployed = {};

  for (let language in languages) {
    let lang = languages[language],
        name = lang.@name.toString(),
        files = parseInt(lang.@files_count, 10),
        code = parseInt(lang.@code, 10),
        comment = parseInt(lang.@comment, 10),
        blank = parseInt(lang.@blank, 10),
        totalLines = code + comment + blank;

    deployed[name] = {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    };
  }

  let total = results.languages.total,
      sum_files = parseInt(total.@sum_files, 10),
      blank = parseInt(total.@blank, 10),
      comment = parseInt(total.@comment, 10),
      code = parseInt(total.@code, 10),
      totalLines = blank + comment + code;

  deployed.summary = {
    sum_files: sum_files,
    blank: blank,
    comment: comment,
    code: code,
    total: totalLines
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.client.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerClientClocs = function(results) {
  let js = results.languages.language.(@name == 'Javascript'),
      files = parseInt(js.@files_count, 10),
      code = parseInt(js.@code, 10),
      comment = parseInt(js.@comment, 10),
      blank = parseInt(js.@blank, 10),
      totalLines = code + comment + blank;

  stats.clocs.client = {
    Javascript: {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    }
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.templates.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerTemplateClocs = function(results) {
  let soy = results.languages.language.(@name == 'Soy'),
      files = parseInt(soy.@files_count, 10),
      code = parseInt(soy.@code, 10),
      comment = parseInt(soy.@comment, 10),
      blank = parseInt(soy.@blank, 10),
      totalLines = code + comment + blank;

  stats.clocs.templates = {
    Soy: {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    }
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.backend.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerBackendClocs = function(results) {
  let js = results.languages.language.(@name == 'Javascript'),
      files = parseInt(js.@files_count, 10),
      code = parseInt(js.@code, 10),
      comment = parseInt(js.@comment, 10),
      blank = parseInt(js.@blank, 10),
      totalLines = code + comment + blank;

  stats.clocs.backend = {
    Javascript: {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    }
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.tests.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerTestClocs = function(results) {
  let languages = results.languages.language,
      tests = stats.clocs.tests = {};

  for (let language in languages) {
    let lang = languages[language],
        name = lang.@name.toString(),
        files = parseInt(lang.@files_count, 10),
        code = parseInt(lang.@code, 10),
        comment = parseInt(lang.@comment, 10),
        blank = parseInt(lang.@blank, 10),
        totalLines = code + comment + blank;

    tests[name] = {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    };
  }

  let total = results.languages.total,
      sum_files = parseInt(total.@sum_files, 10),
      blank = parseInt(total.@blank, 10),
      comment = parseInt(total.@comment, 10),
      code = parseInt(total.@code, 10),
      totalLines = blank + comment + code;

  tests.summary = {
    sum_files: sum_files,
    blank: blank,
    comment: comment,
    code: code,
    total: totalLines
  };
};

/**
 * Callback from the subprocess used to process stats.clocs.precompile.
 * @param {XML} results The XML object containing the cloc stats.
 */
var registerPrecompileClocs = function(results) {
  let js = results.languages.language.(@name == 'Javascript'),
      files = parseInt(js.@files_count, 10),
      code = parseInt(js.@code, 10),
      comment = parseInt(js.@comment, 10),
      blank = parseInt(js.@blank, 10),
      totalLines = code + comment + blank;

  stats.clocs.precompile = {
    Javascript: {
      code: code,
      commment: comment,
      blank: blank,
      files: files,
      total: totalLines
    }
  };
};

// Kick off the process to get the project stats.
var projectThread = spawn(function() {
  // Ignore precompile.js and all of the compiled script files
  // in ~/WAR/root/scripts.  Not doing so completely skews the results.
  let re = '(precompile\.js|dkpoints.*\.js)',
      strClocs = command('cloc --read-lang-def=lang_defs --xml --quiet ' +
                         '--not-match-f=' + re + ' ../'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);

  registerProjectClocs(results);
});
projectThread.run();

var deployedThread = spawn(function() {
  let strClocs = command('cloc --xml --quiet ../WAR'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);
  
  registerDeployedClocs(results);
});
deployedThread.run();

// Kick off the process to get the client-side Javascript stats.
var clientThread = spawn(function() {
  let strClocs = command('cloc --xml --quiet --not-match-f=test --match-f=\.js$ dkpoints/'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);

  registerClientClocs(results);
});
clientThread.run();

// Kick off the process to get the client-side Javascript stats.
var backendThread = spawn(function() {
  let strClocs = command('cloc --xml --quiet --match-f=.*\.js ../WAR/src/'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);

  registerBackendClocs(results);
});
backendThread.run();

// Kick off the process for getting the client-side testing stats.
var testingThread = spawn(function() {
  let strClocs =
      command('cloc --xml --quiet --match-f=.*test\.[js|html] dkpoints/');
  
  let sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);
  
  registerTestClocs(results);
});
testingThread.run();

// Calculate the LOC in precompile.js.
var precompileThread = spawn(function() {
  let strClocs = command('cloc --xml --quiet precompile.js'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);

  registerPrecompileClocs(results);
});
precompileThread.run();

var templatesThread = spawn(function() {
  let strClocs =
          command('cloc --read-lang-def=lang_defs --xml --quiet templates/'),
      sr = new StringReader(strClocs),
      xmlString = sr.skipLines(2).read(),
      results = new XML(xmlString);

  registerTemplateClocs(results);
});
templatesThread.run();

/*
 * While we're waiting, get the uncompressed and gzipped files sizes of
 * the uncompiled and compiled versions of the script.
 */
var pathToCompiled = '../WAR/root/scripts/';
stats.sizes = {
  uncompressed: {
    precompile: fs.size('precompile.js'),
    webkit: fs.size(pathToCompiled + 'dkpoints-webkit.js'),
    gecko: fs.size(pathToCompiled + 'dkpoints-gecko.js'),
    ie: fs.size(pathToCompiled + 'dkpoints-ie.js'),
    opera: fs.size(pathToCompiled + 'dkpoints-opera.js'),
    noEngine: fs.size(pathToCompiled + 'dkpoints.js')
  },
  gzipped: {
    precompile: getGZippedSize('precompile.js'),
    webkit: getGZippedSize(pathToCompiled + 'dkpoints-webkit.js'),
    gecko: getGZippedSize(pathToCompiled + 'dkpoints-gecko.js'),
    ie: getGZippedSize(pathToCompiled + 'dkpoints-ie.js'),
    opera: getGZippedSize(pathToCompiled + 'dkpoints-opera.js'),
    noEngine: getGZippedSize(pathToCompiled + 'dkpoints.js')
  }
};

stats.testCount = require('./count_tests').testCount;

// Wait for outstanding threads to complete.
deployedThread.isDone() ? null : deployedThread.get();
clientThread.isDone() ? null : clientThread.get();
backendThread.isDone() ? null : backendThread.get();
testingThread.isDone() ? null : testingThread.get();
precompileThread.isDone() ? null : precompileThread.get();
projectThread.isDone() ? null : projectThread.get();
templatesThread.isDone() ? null: templatesThread.get();

stats.desc = desc ? desc : 'No comment.';

var report = fs.open(pathToReport, {append: true, charset: 'utf8'});
report.write(JSON.stringify(stats) + '\n');
report.close();

exports.stats = stats;